﻿#include "FITKGraphObjectInteractionBase.h"

// VTK
#include <vtkPoints.h>
#include <vtkVertex.h>
#include <vtkLine.h>
#include <vtkCellArray.h>
#include <vtkPolyData.h>
#include <vtkUnstructuredGrid.h>
#include <vtkPointData.h>
#include <vtkCellData.h>
#include <vtkProperty.h>
#include <vtkGlyph3D.h>
#include <vtkDistanceToCamera.h>
#include <vtkMaskPoints.h>
#include <vtkRegularPolygonSource.h>
#include <vtkPointSource.h>
#include <vtkRenderer.h>
#include <vtkCamera.h>
#include <vtkIntArray.h>
#include <vtkSelectionNode.h>
#include <vtkSelection.h>
#include <vtkExtractSelection.h>
#include <vtkIdTypeArray.h>
#include <vtkIdList.h>
#include <vtkAppendFilter.h>

// Graph
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITKGraphCommons.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKPolygonSource.h"

// Render
#include "FITK_Component/FITKRenderWindowVTK/FITKGraphRender.h"

// Global data
#include "FITK_Kernel/FITKCore/FITKDataRepo.h"

// Data
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaAbstractInteraction.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaAbstractConstraint.h"
#include "FITK_Interface/FITKInterfacePhysics/FITKBoundaryLocation.h"
#include "FITK_Interface/FITKInterfaceModel/FITKAbstractAssInstance.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaRefPoint.h"
#include "FITK_Interface/FITKInterfaceModel/FITKModelSet.h"
#include "FITK_Interface/FITKInterfaceModel/FITKMeshSurface.h"

// Data manager
#include "FITKSetSurfPolyProvider.h"

namespace Exchange
{
    FITKGraphObjectInteractionBase::FITKGraphObjectInteractionBase(AbaqusData::FITKDataCase* caseData, Interface::FITKInteractionLocation* interactionsData)
        : FITKGraphObjectMarkBase(caseData, interactionsData)
    {
        if (!interactionsData)
        {
            return;
        }

        // Set the layer need to be rendered.
        setRenderLayer(0, 1);

        // Save the graph information.
        m_graphInfo.Type = OtherGraph;

        // Save if the bounds are fixed or dynamic.
        m_hasFixedBounds = false;
    }

    FITKGraphObjectInteractionBase::~FITKGraphObjectInteractionBase()
    {
        // Clear data.
        // clearData();

        // Delete poly data.
        //@{
        deleteVtkObj(m_modelIdArray);
        deleteVtkObj(m_points);
        deleteVtkObj(m_ugrid);

        deleteVtkObj(m_gridLoc_1);
        deleteVtkObj(m_gridLoc_2);
        //@}

        // Delete source if necessary.
        deleteVtkObj(m_polygonSource);

//#ifdef QT_DEBUG
        deleteVtkObj(m_pointSource);
//#endif      

        // Delete filters.
        deleteVtkObjs(m_glyphList);
        deleteVtkObjs(m_distToCameraList);
        deleteVtkObjs(m_maskPointsList);

        // Delete extractor pointers.
        deleteVtkObj(m_selectNode);
        deleteVtkObj(m_section);
        deleteVtkObj(m_extractSelection);
        deleteVtkObj(m_idArray);

        // Clear the ids.
        m_subSetIds_1.clear();
        m_subSetIds_2.clear();
    }

    void FITKGraphObjectInteractionBase::setVisible(bool visibility)
    {
        if (m_fActor)
        {
            m_fActor->SetVisibility(visibility);
        }
    }

    void FITKGraphObjectInteractionBase::init()
    {
        // Create poly data.
        //@{
        m_ugrid = vtkUnstructuredGrid::New();
        m_points = vtkPoints::New();
        m_ugrid->SetPoints(m_points);
        m_modelIdArray = vtkIntArray::New();
        m_modelIdArray->SetNumberOfComponents(1);

        m_gridLoc_1 = vtkUnstructuredGrid::New();
        m_gridLoc_2 = vtkUnstructuredGrid::New();
        //@}

        // Initialize the extractor.
        //@{
        m_selectNode = vtkSelectionNode::New();
        m_section = vtkSelection::New();
        m_extractSelection = vtkExtractSelection::New();
        m_selectNode->SetContentType(vtkSelectionNode::INDICES);
        m_selectNode->SetFieldType(vtkSelectionNode::SelectionField::CELL);
        m_section->AddNode(m_selectNode);
        m_extractSelection->SetInputData(1, m_section);

        m_idArray = vtkIdTypeArray::New();
        m_selectNode->SetSelectionList(m_idArray);
        m_extractSelection->SetInputData(m_ugrid);
        //@}
    }

    void FITKGraphObjectInteractionBase::initActors()
    {
        // Get the interactions or constraint color.
        double color3[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_constraintColor, color3);
        
        // Create the actor for lines.
        m_fActor = FITKGraphActor::New();
        m_fActor->setGraphObject(this);
        m_fActor->SetPickable(false);
        m_fActor->setScalarVisibility(false);
        m_fActor->GetProperty()->SetColor(color3);
        m_fActor->GetProperty()->SetLineWidth(2);

        // Add actors.
        addActor(m_fActor);
    }

    void FITKGraphObjectInteractionBase::clearModelsInfo()
    {
        // Clear model ids.
        m_modelIds_1.clear();
        m_modelIds_2.clear();
        m_modelVisibilityHash.clear();
        m_subSetIds_1.clear();
        m_subSetIds_2.clear();

        resetVtkObj(m_modelIdArray);
    }

    void FITKGraphObjectInteractionBase::clearData()
    {
        // Clear data.
        resetVtkObj(m_points);
        resetVtkObj(m_ugrid);
        resetVtkObj(m_gridLoc_1);
        resetVtkObj(m_gridLoc_2);

        // Reset the component ids.
        m_locationCompId_1 = -1;
        m_locationCompId_2 = -1;

        // Clear the sub set ids.
        m_subSetIds_1.clear();
        m_subSetIds_2.clear();

        // Clear the model infomation.
        clearModelsInfo();
    }

    void FITKGraphObjectInteractionBase::highlight(QColor color)
    {
        // If the object is invisible, then return.
        if (!getDataVisibility())
        {
            return;
        }

        // Get the interactions or constraint highlight color.
        double color3[3] = { 0 ,0, 0 };
        if (color.isValid())
        {
            FITKGraphCommons::QColorToDouble3(color, color3);
        }
        else
        {
            FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_constraintHighlightColor, color3);
        }      
        
        // Set the highlight color for glyph.
        if (m_fActor)
        {
            m_fActor->GetProperty()->SetColor(color3);
        }

        FITKGraphObject3D::highlight();
    }

    void FITKGraphObjectInteractionBase::disHighlight()
    {
        // Get the interactions or constraint default color.
        double color3[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_constraintColor, color3);

        // Set the default color for glyph.
        if (m_fActor)
        {
            m_fActor->GetProperty()->SetColor(color3);
        }     

        FITKGraphObject3D::disHighlight();
    }

    void FITKGraphObjectInteractionBase::update(bool forceUpdate)
    {
        // Update poly data after the interactions's set or surface changed.
        Interface::FITKInteractionLocation* interactionsData = dynamic_cast<Interface::FITKInteractionLocation*>(_dataObj);
        if (!m_ugrid || !m_gridLoc_1 || !m_gridLoc_2 || !interactionsData)
        {
            return;
        }

        // Get the location id.
        Interface::FITKBoundaryLocation* location1 = interactionsData->getFirstLocation();
        Interface::FITKBoundaryLocation* location2 = interactionsData->getSecondLocation();
        if (!location1 || !location2)
        {
            return;
        }

        // Get the set( set or ref-node ) or surface id.
        int locationCompId1 = location1->getComponentID();
        int locationCompId2 = location2->getComponentID();

        // Check if the component exists.
        bool isComp1Valid = getComponentValid(locationCompId1);
        bool isComp2Valid = getComponentValid(locationCompId2);

        bool needUpdate = false;
        if (!isComp1Valid || !isComp2Valid)
        {
            needUpdate = true;

            // Clear data.
            clearData();
        }

        // Check if the locations changed.
        needUpdate |= (locationCompId1 != m_locationCompId_1 || locationCompId2 != m_locationCompId_2 || forceUpdate);
        if (!needUpdate)
        {
            // Update the visibility only.
            updateVisibility();
            return;
        }

        // Clear data first.
        clearData();

        bool flag = generatePolyData(interactionsData);
        if (!flag)
        {
            return;
        }

        // Update the visibility at last.
        updateVisibility();

        // Modify actors.
        updateActors();
    }

    void FITKGraphObjectInteractionBase::updateVisibility()
    {
        // Update the full actor.
        FITKGraphObject3D::updateVisibility();

        if (!m_idArray || !m_modelIdArray || !m_ugrid)
        {
            return;
        }

        QHash<int, bool> modelVisibilityHash;

        // Save the visibility first, and check if the visbility has changed at the same time.
        bool hasChanged = false;
        for (int modelId : m_modelIds_1 + m_modelIds_2)
        {
            bool parentVisible = getParentDataVisibility(modelId);
            bool oldVisibility = false;
            if (m_modelVisibilityHash.contains(modelId))
            {
                oldVisibility = m_modelVisibilityHash[modelId];
            }

            if (parentVisible != oldVisibility)
            {
                hasChanged = true;
            }

            if (!modelVisibilityHash.contains(modelId))
            {
                modelVisibilityHash.insert(modelId, parentVisible);
            }
        }

        // If the visibility hasn't changed, then return.
        if (!hasChanged && !m_modelVisibilityHash.isEmpty())
        {
            return;
        }

        // Reset the extractor's ids.
        resetVtkObj(m_idArray);

        // Update the extractor's ids.
        for (int i = 0; i < m_ugrid->GetNumberOfCells(); i++)
        {
            bool visibility = getCellVisibilityWithModel(m_ugrid->GetCell(i), m_modelIdArray, modelVisibilityHash);
            if (visibility)
            {
                m_idArray->InsertNextValue(i);
            }
        }

        // Update the data.
        m_ugrid->Modified();

        // Save the flags.
        m_modelVisibilityHash = modelVisibilityHash;
    }

    bool FITKGraphObjectInteractionBase::generatePolyData(Interface::FITKInteractionLocation* interactionsData)
    {
        // Override in sub-class.
        Q_UNUSED(interactionsData);
        return false;
    }

    bool FITKGraphObjectInteractionBase::generateLocPolyData(Interface::FITKBoundaryLocation* loc, QList<int>& modelIds, QList<int>& subSetIds, vtkUnstructuredGrid* ugrid)
    {
        // Get the location id.
        if (!loc || !ugrid)
        {
            return false;
        }

        // Get the surface id.
        int compId = loc->getComponentID();

        // Try to get the instance id.
        int instanceIdLoc = -1;

        Interface::FITKAbstractAssInstance* instance = loc->getTModelObj<Interface::FITKAbstractAssInstance>();
        if (instance)
        {
            instanceIdLoc = instance->getDataObjectID();
        }

        bool isValid = false;

        // Try to get set and surface poly data for location.
        Interface::FITKModelSet* set = Core::FITKDataRepo::getInstance()->getTDataByID<Interface::FITKModelSet>(compId);
        if (set && !isValid)
        {
            generateSetData(ugrid, set, modelIds, instanceIdLoc);

            // Save the sub set ids.
            subSetIds = FITKSetSurfPolyProvider::GetCompSetSubSetIds(compId);

            isValid = true;
        }

        Interface::FITKMeshSurface* surface = Core::FITKDataRepo::getInstance()->getTDataByID<Interface::FITKMeshSurface>(compId);
        if (surface && !isValid)
        {
            vtkUnstructuredGrid* surfGridLoc = FITKSetSurfPolyProvider::getInstance()->getSurfaceGridData(compId, modelIds, instanceIdLoc);
            if (!surfGridLoc)
            {
                return false;
            }

            ugrid->DeepCopy(surfGridLoc);

            // Save the sub set ids.
            subSetIds = FITKSetSurfPolyProvider::GetSurfaceSubSetIds(compId);

            isValid = true;
        }

        // Remove the normals data.
        if (ugrid)
        {
            FITKGraphCommons::RemoveCellNormals(ugrid);
        }

        return isValid;
    }

    void FITKGraphObjectInteractionBase::generateGraph()
    {
        // Override in sub-class.
    }

    bool FITKGraphObjectInteractionBase::getDataVisibility()
    {
        bool isActive = false;

        // Try to down cast to interaction data.       
        Interface::FITKAbaAbstractInteraction* interaction = dynamic_cast<Interface::FITKAbaAbstractInteraction*>(_dataObj);
        if (interaction)
        {
            isActive = interaction->getActiveState();
        }  

        // Try to down cast to constraint data.       
        Interface::FITKAbaAbstractConstraint* constraint = dynamic_cast<Interface::FITKAbaAbstractConstraint*>(_dataObj);
        if (constraint)
        {
            isActive = constraint->getActiveState();
        }
        
        return isActive;
    }

    void FITKGraphObjectInteractionBase::generateComponentGridData(vtkUnstructuredGrid* setSurfGrid, Interface::FITKBoundaryLocation* loc
        , QList<int>& modelIds, QList<int>& subSetIds, bool removeNormals)
    {
        if (!setSurfGrid || !loc)
        {
            return;
        }

        // Get the location component id.
        int compId = loc->getComponentID();

        // Try to get the instance id.
        int instanceId = -1;

        Interface::FITKAbstractAssInstance* instance = loc->getTModelObj<Interface::FITKAbstractAssInstance>();
        if (instance)
        {
            instanceId = instance->getDataObjectID();
        }

        this->Superclass::generateComponentGridData(compId, setSurfGrid, modelIds, subSetIds, instanceId, removeNormals);
    }

    void FITKGraphObjectInteractionBase::setFITKRender(Comp::FITKGraphRender* render)
    {
        if (!render)
        {
            return;
        }

        vtkRenderer* renderer = render->getRenderer();
        if (!renderer)
        {
            return;
        }

        // Set the renderer for calculating size.
        for (vtkDistanceToCamera* obj : m_distToCameraList)
        {
            if (obj)
            {
                obj->SetRenderer(renderer);
            }
        }

        // Initialize the normal.
        updateCameraNormal();

        // Save the render.
        this->Superclass::setFITKRender(render);
    }

    void FITKGraphObjectInteractionBase::updateCameraNormal()
    {
//#ifdef QT_NO_DEBUG
        if (!m_simplifiedGlyph && m_fRender)
        {
            vtkRenderer* renderer = m_fRender->getRenderer();
            if (!renderer || !m_polygonSource)
            {
                return;
            }

            vtkCamera* camera = renderer->GetActiveCamera();
            if (camera)
            {
                m_polygonSource->SetNormal(camera->GetViewPlaneNormal());
            }
        }
//#endif
    }

    void FITKGraphObjectInteractionBase::simplified(bool enabled)
    {
        if (!m_graphSimplified)
        {
            return;
        }

        // Change the actor visibility while changing the camera.
        if (m_fActor && getDataVisibility())
        {
            m_fActor->SetVisibility(!enabled);
        }
    }

    bool FITKGraphObjectInteractionBase::isRelatedTo(int dataObjId)
    {
        // Check if the id is the self-object id.
        bool isTheObj = FITKGraphObject3D::isRelatedTo(dataObjId);
        if (isTheObj)
        {
            return true;
        }

        // Check if the model ids contains the data id.
        bool hasModelRelated = m_modelIds_1.contains(dataObjId) || m_modelIds_2.contains(dataObjId);
        if (hasModelRelated)
        {
            return true;
        }

        // Check if the location is the same as the data.
        bool hasLocRelated = (m_locationCompId_1 == dataObjId || m_locationCompId_2 == dataObjId);
        if (hasLocRelated)
        {
            return true;
        }

        // Check if the data id is one of the sub set id.
        bool isSubSet = m_subSetIds_1.contains(dataObjId) || m_subSetIds_2.contains(dataObjId);
        if (isSubSet)
        {
            return true;
        }

        return false;
    }

    bool FITKGraphObjectInteractionBase::getFixedBounds(double* bounds)
    {
        // If the graph object is invisible, then return.
        bool visibile = getDataVisibility();
        if (!visibile)
        {
            return false;
        }

        // Get the components' bounds only.
        if (!m_extractSelection)
        {
            return false;
        }

        double bds[6] = { 0., 0., 0., 0., 0., 0. };
        bool flag = FITKGraphCommons::GetDataSetBounds(m_extractSelection->GetOutput(), bds);
        if (!flag)
        {
            return false;
        }

        for (int i = 0; i < 6; i++)
        {
            bounds[i] = bds[i];
        }

        return true;
    }
}   // namespace Exchange